using System;
using System.Linq.Expressions;
using System.Reflection;
using Lucene.Linq.Mapping;

namespace Lucene.Linq.Expressions
{
    internal class ProjectionBuilder : LuceneExpressionVisitor, IProjectionBuilder
    {
        protected static MethodInfo MiGetValue;
        protected static MethodInfo MiExecuteSubQuery;
        protected string DocumentAlias;
        protected ParameterExpression DocumentParameter;

        internal ProjectionBuilder()
        {
            if (MiGetValue == null)
            {
                MiGetValue = typeof (DocumentProjection).GetMethod("GetValue");
                MiExecuteSubQuery = typeof (DocumentProjection).GetMethod("ExecuteSubQuery");
            }
        }

        #region IProjectionBuilder Members

        public virtual LambdaExpression Build(Expression expression, string alias)
        {
            DocumentParameter = Expression.Parameter(typeof (DocumentProjection), "document");
            DocumentAlias = alias;

            // visit the body
            Expression body = Visit(expression);

            // return a lambda wrapping the 
            return Expression.Lambda(body, DocumentParameter);
        }

        #endregion

        protected override Expression VisitField(FieldExpression field)
        {
            if (field.Alias == DocumentAlias)
            {
                return
                    Expression.Convert(
                        Expression.Call(DocumentParameter, MiGetValue, Expression.Constant(field.Name),
                                        Expression.Constant(field.Type)), field.Type);
            }

            return field;
        }

        protected override Expression VisitProjection(ProjectionExpression projection)
        {
            LambdaExpression subQuery = Expression.Lambda(base.VisitProjection(projection), DocumentParameter);
            Type elementType = TypeSystem.GetElementType(subQuery.Body.Type);
            MethodInfo mi = MiExecuteSubQuery.MakeGenericMethod(elementType);
            return Expression.Convert(Expression.Call(DocumentParameter, mi, Expression.Constant(subQuery)),
                                      projection.Type);
        }
    }
}